package net.yc.web.imalachat.push.factory;

import com.google.common.base.Strings;
import net.yc.web.imalachat.push.bean.db.User;
import net.yc.web.imalachat.push.bean.db.UserFollow;
import net.yc.web.imalachat.push.utils.Hib;
import net.yc.web.imalachat.push.utils.TextUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * @author: lyc
 * @data: 2021-12-27  13:16
 * @Description:
 */
public class UserFactory {

    /**
     * 最重要的一个方法，通过token查询用户信息
     * 只能自己使用，查询的信息是个人信息，非他人信息
     * @param token
     * @return
     */
    public static User findByToken(String token) {
        return Hib.query(session -> (User) session.createQuery("from User where token =:token")
                .setParameter("token", token)
                .uniqueResult());
    }

    /**
     * 根据手机号查询
     * @param phone
     * @return
     */
    public static User findByPhone(String phone) {
        return Hib.query(session -> (User) session
                .createQuery("from User where phone =:inPhone")
                .setParameter("inPhone", phone)
                .uniqueResult());
    }

    public static User findByName(String name) {
        return Hib.query(session -> (User) session
                .createQuery("from User where name =:inName")
                .setParameter("inName", name)
                .uniqueResult());
    }

    /**
     * 通过主键id查询用户
     * @param id
     * @return
     */
    public static User findById(String id) {
        // id 是主键，查询起来更加方便，直接就是下面这个样子就行
        return Hib.query(session -> session.get(User.class, id));
    }

    /**
     * 更新信息到数据库
     * @param user
     * @return
     */
    public static User update(User user) {
        return Hib.query(session -> {
            session.saveOrUpdate(user);
            return user;
        });
    }

    /**
     * 使用账号和密码进行登录
     * @return
     */
    public static User login(String account, String password) {
        String accountStr = account;
        // 把原文进行同样的加密，再进行匹配
        String encodePassword = encodePassword(password);

        User user =  (User) Hib.query(session -> session.createQuery("from User where phone =:phone and password =:password")
                .setParameter("phone", accountStr)
                .setParameter("password", encodePassword)
                .uniqueResult());

        if (user != null) {
            // 对user进行登录
            user = login(user);
        }
        return user;
    }

    /**
     * 给当前用户的账户绑定PushId
     * @param user 自己的User
     * @param pushId 自己的设备
     * @return
     */
    public static User bindPushId(User user, String pushId) {

        if (Strings.isNullOrEmpty(pushId)) {
            return null;
        }

        // 1. 查询是否有其他账户绑定了这个设备，避免另外一个设备也能收到推送
        // 如果已经有其他账户绑定了这个设备，则取消绑定
        // 查询的列表不包括自己，查到的结果就是绑定了这个设备的其他用户
        Hib.queryOnly(session -> {
            @SuppressWarnings("unchecked")
            List<User> userList = (List<User>) session
                    // pushId 可能有大写，也可能有小写，我们要忽略大小写，使用一个lower
                    .createQuery("from User where lower(pushId) =:pushId and id !=:userId ")
                    .setParameter("pushId", pushId.toLowerCase())
                    .setParameter("userId", user.getId())
                    .list();

            for (User u : userList) {
                // 解绑操作
                u.setPushId(null);
                session.saveOrUpdate(u);
            }
        });

        if (pushId.equalsIgnoreCase(user.getPushId())) {
            // 说明这个设备之前绑定过了，就不需要额外绑定了
            return user;
        } else {
            // 当前用户需要绑定的设备id和之前绑定过的不一样
            // 那么需要单点登录，让之前的设备退出账号
            // 给之前的设备推送一条退出消息
            if (!Strings.isNullOrEmpty(pushId)) {
                // TODO 给设备推送一条退出登录的消息
            }

            // 更新新的设备ID
            user.setPushId(pushId);
            return update(user);

        }
    }

    /**
     * 用户注册
     * 注册的操作需要写入数据库，并返回数据库中的User消息
     * @return
     */
    public static User register(String account, String password, String name) {
        // 去除账号中的首尾空格
        account = account.trim();
        // 处理密码
        password = encodePassword(password);
        User user = createUser(account, password, name);
        if (user != null) {
            user = login(user);
        }
        return user;

    }

    /**
     * 创建一个用户并存储到数据库中
     * @param account 账号，也就是手机号
     * @param password 加密后的密码
     * @param name 名字
     * @return
     */
    private static User createUser(String account, String password, String name) {
        User user = new User();
        user.setName(name);
        user.setPassword(password);
        user.setPhone(account);
        // 开始做一个保存
        // 数据库存储
        return Hib.query(session -> {
            // save返回的是一个Serializable主键
            session.save(user);
            return user;
        });
    }

    /**
     * 把一个User进行登录操作，需要把当前的额token更新掉
     * 登录本质上就是对token进行操作
     * @param user
     * @return
     */
    private static User login(User user) {
        // 使用一个随机的UUID值充当token
        String newToken = UUID.randomUUID().toString();
        // 进行一次Base64格式化
        newToken = TextUtil.encodeBase64(newToken);
        user.setToken(newToken);

        return update(user);
    }

    /**
     * 密码的加密
     * @param password 原文
     * @return 密文
     */
    private static String encodePassword(String password) {
        password = password.trim();

        // 进行Md5非对称加密，加盐会更加安全，盐也要存储
        password = TextUtil.getMD5(password);
        // 再进行一个对称的Base64加密，当然也可以草去加盐的方案
        return TextUtil.encodeBase64(password);
    }

    /**
     * 为什么要在一个事务中执行查询操作而不是直接去self.getFollowing()
     * 因为user是在另外一个地方拿到的，是在getToken()里面通过token拿到的
     * 而getToken()的时候，开启了一个事务并拉取了用户信息，但是事务完成之后session就被销毁掉了
     * session就不再持有，无法通过session对user的信息进行刷新操作了
     * 而且getFollowing()是懒加载，也就是getFollowing()在返回数据的时候是没有数据的
     * 如果想要getFollowing()返回有数据的话，首先就要在一个事务里面使用session重新加载一次
     * @param self
     * @return
     */
    public static List<User> contacts(User self) {
        return Hib.query(session -> {
            // 重新加载一次用户信息到self中，和当前的session绑定
            session.load(self, self.getId());
            // 获取我关注的人
            Set<UserFollow> follows = self.getFollowing();

//            List<User> users = new ArrayList<>();
//            for(UserFollow flow : follows) {
//                users.add(flow.getTarget());
//            }
            // 下面的方法等同上面的循环
            return follows.stream()
                    .map(UserFollow::getTarget)
                    .collect(Collectors.toList());
        });
    }

    /**
     * 关注别人的操作
     * @param origin 发起者
     * @param target 被关注的人
     * @param alias 被关注人的备注
     * @return 返回被关注人的信息
     */
    public static User follow(final User origin, final User target, final String alias) {
        UserFollow userFollow = getUserFollow(origin, target);
        if (userFollow != null){
            // 代表已经关注好了，直接返回两个人的关注状态
            return userFollow.getTarget();
        }

        return Hib.query(session -> {
            // 想要直接操作懒加载的数据，需要重新load一次
            session.load(origin, origin.getId());
            session.load(target, target.getId());

            UserFollow originFollow = new UserFollow();
            originFollow.setOrigin(origin);
            originFollow.setTarget(target);
            // 备注是发起者对被关注者的，被关注者默认对发起者不做备注
            originFollow.setAlias(alias);

            UserFollow targetFollow = new UserFollow();
            targetFollow.setOrigin(target);
            targetFollow.setTarget(origin);

            session.save(originFollow);
            session.save(targetFollow);

            return target;
        });
    }

    /**
     * 查询两个人是否已经关注
     * @param origin 发起者
     * @param target 目标关注者
     * @return
     */
    public static UserFollow getUserFollow(final User origin, final User target) {
        return (UserFollow) Hib.query(session -> session.createQuery("from UserFollow where originId =:originId and targetId =:targetId")
                .setParameter("originId", origin.getId())
                .setParameter("targetId", target.getId())
                .setMaxResults(1)
                // 查询一条数据即可
                .uniqueResult());
    }

    /**
     * 搜索联系人的实现
     * @param name 查询的name，允许为空
     * @return 查询到的用户集合，如果name为空，则返回最近的用户
     */
    @SuppressWarnings("unchecked")
    public static List<User> search(String name) {
        // 保证不为空的情况，减少后面的判断和额外的错误
        if (Strings.isNullOrEmpty(name)) {
            name = "";
        }
        // 模糊查询
        final String searchName = "%" + name + "%";
        return Hib.query(session -> {
            // 查询的条件：name忽略大小写，并且使用like（模糊）查询
            // 头像和我描述必须完善才能查询到
            return (List<User>)session.createQuery("from User where lower(name) like :name and portrait is not null and description is not null ")
                    .setParameter("name", searchName)
                    .setMaxResults(20)
                    .list();
        });
    }
}
